---
title: Quickstart
description: Let's get started, making a basic contract, server, and client.
---

<Steps>
  <Step>
    <p>Create a contract with `@ts-rest/core`</p>
  </Step>
  <Step>
    <p>Create a server implementation with one of our supported frameworks</p>
  </Step>
  <Step>
    <p>Create a client implementation with one of our supported libraries</p>
  </Step>
</Steps>

## Installation

Install the `@ts-rest/core` package, this is the core package that provides the type-safe contract, and a basic fetch based client.

<InstallTabs packageName="@ts-rest/core" />

Enable `strict` in your `tsconfig.json`, this is required to work with some ts-rest functionality, and often downstream libraries like [Zod](https://github.com/colinhacks/zod#requirements)

```json title="tsconfig.json"
  "compilerOptions": {
    "strict": false // [!code --]
    "strict": true // [!code ++]
  }
```

## Create a contract

This should ideally be shared between your consumers and producers, e.g. in a shared library in a monorepo, or a shared npm package. Think of this as your HTTP Schema that both your client and backend can use.

<Callout type="info" title="Shared contract">
  We strongly reccomend using a validation library like Zod, Valibot, or Arktype
  to define your contract.

This provides **runtime validation** of your contract, as opposed to just type safety.

</Callout>

<Tabs items={['Zod', 'Valibot', 'Arktype', 'None']}>
  <Tab value="Zod">

```typescript title="contract.ts"
import { initContract } from '@ts-rest/core';
import { z } from 'zod';

const c = initContract();

const Pokemon = z.object({
  name: z.string(),
});

export const pokemonContract = c.router({
  getPokemon: {
    method: 'GET',
    path: '/pokemon/:id',
    responses: {
      200: Pokemon,
    },
    summary: 'Get a pokemon by id',
  },
});
```

  </Tab>
  <Tab value="Valibot">

```typescript title="contract.ts"
import { initContract } from '@ts-rest/core';
import * as v from 'valibot';

const c = initContract();

const Pokemon = v.object({
  name: v.string(),
});

export const pokemonContract = c.router({
  getPokemon: {
    method: 'GET',
    path: '/pokemon/:id',
    responses: {
      200: Pokemon,
    },
    summary: 'Get a pokemon by id',
  },
});
```

  </Tab>
  <Tab value="Arktype">

```typescript title="contract.ts"
import { initContract } from '@ts-rest/core';
import { type } from 'arktype';

const c = initContract();

const Pokemon = type({
  name: 'string',
});

export const pokemonContract = c.router({
  getPokemon: {
    method: 'GET',
    path: '/pokemon/:id',
    responses: {
      200: Pokemon,
    },
    summary: 'Get a pokemon by id',
  },
});
```

  </Tab>
  <Tab value="None">

```typescript title="contract.ts"
import { initContract } from '@ts-rest/core';

const c = initContract();

type Pokemon = {
  name: string;
};

export const pokemonContract = c.router({
  getPokemon: {
    method: 'GET',
    path: '/pokemon/:id',
    responses: {
      200: c.type<Pokemon>(),
    },
    summary: 'Get a pokemon by id',
  },
});
```

  </Tab>
</Tabs>

## Server Implementation

<Tabs items={['Nest', 'Express', 'Fastify', 'Next']}>
  <Tab value="Nest">
  <InstallTabs packageName="@ts-rest/nest" />
<p>ts-rest offers a unique way to create a fully type safe REST API server,
normally Nest APIs are extremely powerful, but hard to make type safe.</p>

Let's add `@ts-rest/nest` to a basic Nest controller:

```typescript title="pokemon.controller.ts"
import { TsRestHandler, tsRestHandler } from '@ts-rest/nest';
import { Controller } from '@nestjs/common';
import { pokemonContract } from './contract';

@Controller()
export class PokemonController {
  constructor(private readonly pokemonService: PokemonService) {}

  @TsRestHandler(pokemonContract.getPokemon)
  async getPokemon() {
    return tsRestHandler(pokemonContract.getPokemon, async ({ params }) => {
      const pokemon = await this.pokemonService.getPokemon(params.id);

      if (!pokemon) {
        return { status: 404, body: null };
      }

      return { status: 200, body: pokemon };
    });
  }
}
```

You can see that we're using the modern `@TsRestHandler` decorator with the `tsRestHandler` function to get full type safety. The `params` are automatically typed based on your contract, and the return type is enforced to match your contract's response schema.

  </Tab>
  <Tab value="Express">
  <InstallTabs packageName="@ts-rest/express" />
<p>The express implementaton allows full type safety, offering; body parsing, query parsing, param parsing and full error handling</p>

```typescript title="main.ts"
import express from 'express';
import cors from 'cors';
import bodyParser from 'body-parser';
import { initServer } from '@ts-rest/express';
import { createExpressEndpoints } from '@ts-rest/express';
import { pokemonContract } from './contract';

const app = express();

app.use(cors());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

const s = initServer();

const router = s.router(pokemonContract, {
  getPokemon: async ({ params: { id } }) => {
    // Mock pokemon data
    const pokemon = { name: 'Pikachu' };

    if (id !== '1') {
      return {
        status: 404,
        body: null,
      };
    }

    return {
      status: 200,
      body: pokemon,
    };
  },
});

createExpressEndpoints(pokemonContract, router, app);

const port = process.env.port || 3333;
const server = app.listen(port, () => {
  console.log(`Listening at http://localhost:${port}`);
});
```

  </Tab>
   <Tab value="Fastify">
  <InstallTabs packageName="@ts-rest/fastify" />
<p>The fastify implementaton allows full type safety, offering; body parsing, query parsing, param parsing and full error handling</p>

```typescript title="main.ts"
import fastify from 'fastify';
import { initServer } from '@ts-rest/fastify';
import { pokemonContract } from './contract';

const app = fastify();

const s = initServer();

const router = s.router(pokemonContract, {
  getPokemon: async ({ params: { id } }) => {
    // Mock pokemon data
    const pokemon = { name: 'Pikachu' };

    if (id !== '1') {
      return {
        status: 404,
        body: null,
      };
    }

    return {
      status: 200,
      body: pokemon,
    };
  },
});

app.register(s.plugin(router));

const start = async () => {
  try {
    await app.listen({ port: 3000 });
  } catch (err) {
    app.log.error(err);
    process.exit(1);
  }
};

start();
```

  </Tab>
  <Tab value="Next">
  <InstallTabs packageName="@ts-rest/next" />

```typescript title="pages/api/[...ts-rest].tsx"
import { createNextRoute, createNextRouter } from '@ts-rest/next';
import { pokemonContract } from './contract';

const router = createNextRoute(pokemonContract, {
  getPokemon: async ({ params: { id } }) => {
    // Mock pokemon data
    const pokemon = { name: 'Pikachu' };

    if (id !== '1') {
      return {
        status: 404,
        body: null,
      };
    }

    return {
      status: 200,
      body: pokemon,
    };
  },
});

// Actually initiate the collective endpoints
export default createNextRouter(pokemonContract, router);
```

  </Tab>
</Tabs>

## Client Implementation

<Tabs items={['Fetch', 'React Query']}>
  <Tab value="Fetch">

This is the basic client, using fetch under the hood which is exported
from @ts-rest/core.

```typescript title="client.ts" twoslash
import { initContract } from '@ts-rest/core';

const c = initContract();

type Pokemon = {
  name: string;
};

export const pokemonContract = c.router({
  getPokemon: {
    method: 'GET',
    path: '/pokemon/:id',
    responses: {
      200: c.type<Pokemon>(),
    },
    summary: 'Get a pokemon by id',
  },
});

// ---cut---
import { initClient } from '@ts-rest/core';

const client = initClient(pokemonContract, {
  baseUrl: 'http://localhost:3000',
  baseHeaders: {},
});

const res = await client.getPokemon({
  params: { id: '1' },
});

if (res.status === 200) {
  res.body.name;
  //   ^?
} else {
  res;
  // ^?
}
```

  </Tab>
  <Tab value="react-query">
  <InstallTabs packageName="@ts-rest/react-query" />

The `@ts-rest/react-query` integration follows the same underlying pattern as the core `@tanstack/react-query` package so it should feel super familiar.

```tsx title="pokemon-component.tsx" twoslash
import React from 'react';
import { initContract } from '@ts-rest/core';

const c = initContract();

type Pokemon = {
  name: string;
};

export const pokemonContract = c.router({
  getPokemon: {
    method: 'GET',
    path: '/pokemon/:id',
    responses: {
      200: c.type<Pokemon>(),
    },
    summary: 'Get a pokemon by id',
  },
});

// ---cut---
import { initQueryClient } from '@ts-rest/react-query';

export const client = initQueryClient(pokemonContract, {
  baseUrl: 'http://localhost:3333',
  baseHeaders: {},
});

export const PokemonComponent = () => {
  const { data, isLoading, error } = client.getPokemon.useQuery(
    ['pokemon', '1'],
    {
      params: { id: '1' },
    },
  );

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (data?.status !== 200 || error) {
    // ^?
    return <div>Pokemon not found</div>;
  }

  return <div>{data.body.name}</div>;
};
```

  </Tab>
</Tabs>
